from threading import Thread
from time import sleep

import libtcodpy as libtcod

from utils import Key, Color


def key_tcod_to_udoc(key_event):
    """Convert libtcod.Key key event object to utils.Key UDoC object."""
    char = ""
    if key_event.vk == libtcod.KEY_CHAR:
        char = chr(key_event.c)
    else:
        char = Key.SPECIAL_KEYS[key_event.vk]
    return Key(char, key_event.lctrl, key_event.rctrl, key_event.lalt,
               key_event.ralt, key_event.shift)


def key_udoc_to_tcod(key):
    """Convert utils.Key UDoC object to libtcod.Key key event object."""
    key_event = libtcod.Key()
    if "lctrl" in key.flags:
        key_event.lctrl = True
    if "rctrl" in key.flags:
        key_event.rctrl = True
    if "lalt" in key.flags:
        key_event.lalt = True
    if "ralt" in key.flags:
        key_event.ralt = True
    if "shift" in key.flags:
        key_event.shift = True
    if key.char in Key.SPECIAL_KEYS:
        key_event.vk = Key.SPECIAL_KEYS.index(key.char)
    else:
        key_event.vk = libtcod.KEY_CHAR
        key_event.c = ord(key.char)


def color_tcod_to_udoc(color):
    """Convert libtcod.Color object to utils.Color UDoC object."""
    return Color(color.r, color.g, color.b)


def color_udoc_to_tcod(color):
    """Convert utils.Color UDoC object to libtcod.Color object."""
    return libtcod.Color(color.r, color.g, color.b)


class Renderer(Thread):
    """Class that renders the game and handles inputs."""
    LIBTCOD_FPS_CAP = 30

    def __init__(self, game, map_lock, screen_width, screen_height, title="Ultimate Dungeons of Chaos", font="font.png"):
        """
        game - main Game object
        map_lock - threading.Lock objects that controls locking the Map
        screen_width - width of window in tiles
        screen_height - height of window in tiles
        title - title of window
        font - filename of font
        """
        super().__init__()
        self.game = game
        self.map_lock = map_lock
        self.screen_height = screen_height
        self.screen_width = screen_width
        self.map_view_width = screen_width  # temporary
        self.map_view_height = screen_height  # this too
        self.frame_count = 0
        self.is_running = True
        self.cache_player_x = 0  # Player position.
        self.cache_player_y = 0

        libtcod.console_set_custom_font(
            font.encode("ascii"), libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
        libtcod.console_init_root(screen_width, screen_height,
                                  title.encode("ascii"), False)
        libtcod.sys_set_fps(self.LIBTCOD_FPS_CAP)
        self.con = libtcod.console_new(
            self.game.width, self.game.height)  # Console for map.

    def run(self):
        """Main Renderer loop."""
        while self.is_running:
            self.map_lock.acquire()
            if self.game.map.active_layer.changed:
                self.game.map.active_layer.changed = False
                self.load_map()
                self.draw(
                )  # Fix because Windows, libtcod and threading cause bugs (not displaying characters)
            self.map_lock.release()
            self.draw()
            self.frame_count += 1
        libtcod.console_delete(0)

    def draw(self):
        """Plots part of con console on screen."""
        x0 = max(0, self.cache_player_x - self.map_view_width // 2)
        x0 = min(self.game.width - self.map_view_width, x0)
        y0 = max(0, self.cache_player_y - self.map_view_height // 2)
        y0 = min(self.game.height - self.map_view_height, y0)
        libtcod.console_blit(
            self.con, x0, y0, self.map_view_width, self.map_view_height, 0, 0, 0)
        libtcod.console_flush()

    def load_map(self):  # TODO: Optimize this somehow. (Preloading?)
        """Read map of game and print changed tiles into con console."""
        self.cache_player_x = self.game.map.player.x
        self.cache_player_y = self.game.map.player.y
        layer = self.game.map.active_layer
        for x in range(self.game.width):
            for y in range(self.game.height):
                tile = layer.get(x, y)
                in_fov = libtcod.map_is_in_fov(layer.tcod_map, x, y)
                fov_changed = libtcod.map_is_in_fov(
                    layer.tcod_map, x, y) != libtcod.map_is_in_fov(layer.old_tcod_map, x, y)
                if (tile.changed and in_fov) or fov_changed:
                    tile.changed = False
                    render_repr = tile.render_repr
                    if not in_fov:
                        render_repr = render_repr.hidden_version
                    fg = color_udoc_to_tcod(render_repr.fg)
                    bg = color_udoc_to_tcod(render_repr.bg)
                    libtcod.console_put_char_ex(
                        self.con,
                        x,
                        y,
                        render_repr.char,
                        fg,
                        bg)
        libtcod.map_copy(layer.tcod_map, layer.old_tcod_map)

    def get_next_key(self):
        """Get next key from keyboard."""
        self.map_lock.release()
        key = libtcod.console_wait_for_keypress(False)
        while not key.pressed:
            key = libtcod.console_wait_for_keypress(False)
        if key.vk == libtcod.KEY_ESCAPE:
            self.is_running = False
            # quit()  # Uh oh, rework this later. (After remaking keyboard
            # input system and putting it into Game.)
        self.map_lock.acquire()
        return key_tcod_to_udoc(key)
